﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;
using itau.im.negocio;
using Itau.SC.EnterpriseLib.Logging;
using System.Xml;
using System.IO;
using EnterpriseLibraryWrapper;
using LogOnline;

using itau.im.entidade;
using itau.imnk.entidades;
using itau.imnk.negocios;
using itau.im.negocio.conversor;


namespace itau.imnk.transcritor
{
    public class Transcritor
    {
        private static string IDENTFICADOR_ROTINA = "IMM6202";

        private const int PERIODO_PATROL_MINUTOS = 3;

        private const int SITUACAO_EXECUCAO_AGENDADA = 11;
        private const int SITUACAO_EXECUCAO_SOLICITADA_UPLOAD = 14;
        private const int SITUACAO_EXECUCAO_AGUARDANDO_LIBERACAO = 17;
        private const int SITUACAO_EXECUCAO_LIBERADA = 40;

        public const int VERTICE = 4;
        public const int EXPONENCIAL = 1;
        public const int LINEAR = 2;
        public const int FATOR_DIARIO = 3;
        public const int PU = 4;
        public const int TAXA = 7;

        public const int DU252 = 1;
        public const int DC360 = 2;
        public const int DC30 = 3;
        public const int OVER = 4;

        private int _tempoEsperaProximaTranscricao;
        private int _tempoEntreImportacaoExecucao;
        private int _tempoToleranciaParaObtencaoYield;
        private bool _enviarEmailToleranciaExtrapolada;
        private bool _statusAtivo;
        private bool _custos;
        private DateTime _ultimaTranscricao;
        private DateTime _fimTranscricao;
        private DateTime _dataHoraAtual;

        private Configuracoes _configuracoes;
        private GeradorRelatorio _geradorRelatorio;
        private EmailSender _emailSender;

        private ConversorTaxas _conversorTaxas;

        private wILog _logOn;

        private Timer _timerPatrol = null;

        public Transcritor()
        {
            _conversorTaxas = new ConversorTaxas();
            _logOn = new wILogClass();

            _logOn.IniciarLog(ref IDENTFICADOR_ROTINA);
        }

        public void Transcrever()
        {
            try
            {
                Console.WriteLine("Transcritor ...");

                TranscreverCurvas();
            }
            catch (Exception ex)
            {
                EnterpriseLibraryFactory.GetExceptionHandler().HandleException(ex, "ErrosTranscritor");
            }
            finally
            {
                LimparObjetos();
            }
        }

        ~Transcritor()
        {
            LimparObjetos();
        }

        private void TranscreverCurvas()
        {
            Logger.RegistrarLog("Inicio");

            CriarTimerPatrol();

            while (true)
            {
                try
                {
                    _dataHoraAtual = DateTime.Now;

                    if (DiaUtil(_dataHoraAtual))
                    {
                        ObterParametrosTranscricao();

                        CopiarCurvasNKIM();

                        AguardarProximaTranscricao();
                    }
                    else
                    {
                        AguardarAteDiaUtil(_dataHoraAtual);
                    }
                }
                catch (Exception ex)
                {
                    EnterpriseLibraryFactory.GetExceptionHandler().HandleException(ex, "ErrosTranscritor");

                    Console.WriteLine(ex.Message);

                    AguardarProximaTranscricao();
                }
            }
        }

        private void CopiarCurvasNKIM()
        {
            if (_statusAtivo)
            {
                ExecucaoCurvaBS execucaoCurvaBS = new ExecucaoCurvaBS();

                //Obtém curvas do arquivo de configurações
                IList<Configuracoes.CurvaGravacao> curvasParaGravacao = _configuracoes.Transcricao.CurvasGravacao;

                IList<RegistroExecucaoCurva> listaRegistroExecucao = execucaoCurvaBS.ObterRegistrosExecucaoCurva(_dataHoraAtual.AddMinutes(_tempoEntreImportacaoExecucao), SITUACAO_EXECUCAO_AGENDADA, SITUACAO_EXECUCAO_SOLICITADA_UPLOAD);

                foreach (Configuracoes.CurvaGravacao item in curvasParaGravacao)
                {
                    try
                    {
                        Curva curvaIM = ObterCurvaIMPorNome(item.NomeCurvaIM);
                        //alteração naka
                        //se for custos = true , gera custos

                        if (_custos == true)
                        {
                            item.NomeYieldNK = "CUSTOS";
                        }
                        if (_custos == false)
                        {
                            item.NomeYieldNK = "ONLINE";
                        }
             

                        var proximaHoraExecucaoGrade = ObterProximoHorarioGrade(curvaIM);

                        //Verifica se haverá agendamento dentro do período estipulado
                        if (proximaHoraExecucaoGrade != DateTime.MinValue &&
                            proximaHoraExecucaoGrade <= _dataHoraAtual.AddMinutes(_tempoEntreImportacaoExecucao))
                        {
                            GerarArquivo(curvaIM.CurvaVigente, proximaHoraExecucaoGrade, item);
                        }
                        else
                        {
                            var listaProximasExecucoes = from exec in listaRegistroExecucao
                                                         where exec.CodigoCurva == curvaIM.CodigoCurva &&
                                                               //exec.DataHoraExecucaoCurva > _dataHoraAtual
                                                               exec.DataHoraExecucaoCurva.AddMinutes(60) > _dataHoraAtual
                                                         orderby exec.DataHoraExecucaoCurva
                                                         select exec;

                            if (listaProximasExecucoes.Count() > 0)
                            {
                                //Verifica se há registro já cadastrado com hora de execução dentro do período estipulado
                                var proximaExecucaoCadastrada = listaProximasExecucoes.First();

                                if (proximaExecucaoCadastrada.DataHoraExecucaoCurva <= _dataHoraAtual.AddMinutes(_tempoEntreImportacaoExecucao))
                                {
                                    GerarArquivo(curvaIM.CurvaVigente, proximaExecucaoCadastrada.DataHoraExecucaoCurva, item);
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        _emailSender.AdicionarLinhaMensagem(ex.Message);

                        Console.WriteLine(ex.Message);

                        EnterpriseLibraryFactory.GetExceptionHandler().HandleException(ex, "ErrosTranscritor");
                    }
                }
            }
        }

        private void GerarArquivo(CurvaVigente curvaVigente, DateTime dataHoraExecucaoCurva, Configuracoes.CurvaGravacao curvaGravacao)
        {
           

            IList<Informacao> yieldDoDia = ObterUltimaPublicacaoYield(curvaGravacao.TipoYieldNK, curvaGravacao.NomeYieldNK, _dataHoraAtual.Date);

            if (yieldDoDia.Count > 0)
            {
                // valida a data e hora da online
                //custos - tem que ser do mesmo dia
                //online - intervalo maximo em minutos configurado no config
                ValidarLimiteToleranciaHoraPublicacao(yieldDoDia.First(), curvaGravacao.NomeYieldNK);

                IList<CurvaExecucaoPonto> listaPontos = ConverterPontos(curvaVigente, dataHoraExecucaoCurva, yieldDoDia);
                IList<CurvaExecucaoPonto> listaPontosOrdenados = listaPontos.OrderBy(p => p.QuantidadeDiasCorridos).ToList();

                string nomeArquivo = string.Concat(curvaGravacao.EnderecoGravacao, curvaGravacao.NomeCurvaIM, ".csv");

                using (StreamWriter streamWriter = new StreamWriter(nomeArquivo))
                {
                    //Grava o cabeçalho
                    string cabecalho = FormatarLinha(
                        ImportadorArquivoVertices.NOME_CAMPO_PRAZO,
                        ImportadorArquivoVertices.NOME_CAMPO_VALOR,
                        ImportadorArquivoVertices.NOME_CAMPO_DATABASE);

                    //Grava a primeira linha com cabeçalho
                    string primeiraLinha = FormatarLinha(
                        listaPontosOrdenados[0].QuantidadeDiasCorridos,
                        listaPontosOrdenados[0].ValorVertice.ToString("N14"),
                        dataHoraExecucaoCurva.ToString("dd/MM/yyyy"));
                    
                    streamWriter.WriteLine(cabecalho);
                    streamWriter.WriteLine(primeiraLinha);

                    //Grava as demais linhas
                    for (int i = 1; i < listaPontosOrdenados.Count; i++)
                    {
                        string linha = FormatarLinha(
                            listaPontosOrdenados[i].QuantidadeDiasCorridos,
                            listaPontosOrdenados[i].ValorVertice.ToString("N14"));

                        streamWriter.WriteLine(linha);
                    }
                }
            }
        }

        private string FormatarLinha(object colunaPrazo, object colunaValor, object colunaDataBase)
        {
            return string.Concat(colunaPrazo, ";", colunaValor, ";", colunaDataBase);
        }

        private string FormatarLinha(object colunaPrazo, object colunaValor)
        {
            return string.Concat(colunaPrazo, ";", colunaValor);
        }

        public IList<CurvaExecucaoPonto> ConverterPontos(CurvaVigente curvaVigente, DateTime dataHoraExecucaoCurva, IList<Informacao> listaInformacao)
        {
            DateTime ultimaDataVencimento = listaInformacao.Max(p => p.DataVencimento);
            CalendarioReguaBS calendarioRegua = new CalendarioReguaBS(dataHoraExecucaoCurva, ultimaDataVencimento.AddDays(1));

            TaxaPontoCurvaVigente tipoVertice = RetornaTipoVertice(curvaVigente);

            IList<CurvaExecucaoPonto> listaPontos = new List<CurvaExecucaoPonto>();
            ConversorTaxas conversorTaxas = new ConversorTaxas();

            foreach (Informacao itemInformacao in listaInformacao)
            {
                CurvaExecucaoPonto curvaExecucaoPonto = new CurvaExecucaoPonto()
                {
                    DataVencimento = itemInformacao.DataVencimento,
                    QuantidadeDiasCorridos = ObterDiasCorridos(dataHoraExecucaoCurva, itemInformacao.DataVencimento),
                    QuantidadeDiasUteis = ObterDiasUteis(calendarioRegua, itemInformacao.DataVencimento)
                };

                ConverterPontoVertice(curvaExecucaoPonto, curvaVigente, tipoVertice, itemInformacao, conversorTaxas);

                listaPontos.Add(curvaExecucaoPonto);
            }

            return listaPontos;
        }

        public int ObterDiasCorridos(DateTime dataBase, DateTime dataVencimento)
        {
            return Convert.ToInt32(dataVencimento.Date.ToOADate() - dataBase.Date.ToOADate());
        }

        public int ObterDiasUteis(CalendarioReguaBS calendarioRegua, DateTime dataVencimento)
        {
            return calendarioRegua.ObterQuantidadeDiasUteis(dataVencimento.ToString("yyyyMMdd"));
        }

        public void ConverterPontoVertice(CurvaExecucaoPonto pontoCurva, CurvaVigente curvaVigente, TaxaPontoCurvaVigente tipoVertice, Informacao informacao, ConversorTaxas conversorTaxas)
        {
            if (informacao.CodigoTipoInformacao == "DI")
            {
                switch (tipoVertice.CodigoTipoTaxa)
                {
                    case TAXA:
                        if (curvaVigente.CodigoTipoTaxa == EXPONENCIAL)
                        {
                            pontoCurva.ValorVertice = conversorTaxas.ConvertePUParaExponencial(informacao.ValorPUTaxa, RetornarDiasAnoPorTipoBase(curvaVigente.CodigoTipoPrazo), RetornarNumeroDias(curvaVigente.CodigoTipoPrazo, pontoCurva.QuantidadeDiasCorridos, pontoCurva.QuantidadeDiasUteis));
                        }
                        else if (curvaVigente.CodigoTipoTaxa == LINEAR)
                        {
                            pontoCurva.ValorVertice = conversorTaxas.ConvertePUParaLinear(informacao.ValorPUTaxa, RetornarDiasAnoPorTipoBase(curvaVigente.CodigoTipoPrazo), RetornarNumeroDias(curvaVigente.CodigoTipoPrazo, pontoCurva.QuantidadeDiasCorridos, pontoCurva.QuantidadeDiasUteis));
                        }
                        break;
                    case FATOR_DIARIO:
                        pontoCurva.ValorVertice = conversorTaxas.ConverterPUParaFatorDiario(informacao.ValorPUTaxa);
                        break;
                    case PU:
                    default:
                        pontoCurva.ValorVertice = informacao.ValorPUTaxa;
                        break;
                }
            }
            else if (informacao.CodigoTipoInformacao == "OVER") //Se OVER, então o vértice deverá ser convertido automaticamente
            {
                pontoCurva.ValorVertice = conversorTaxas.ConverterTaxaExponencialParaFatorDiario(informacao.ValorPUTaxa252, RetornarDiasAnoPorTipoBase(curvaVigente.CodigoTipoPrazo), RetornarNumeroDias(curvaVigente.CodigoTipoPrazo, pontoCurva.QuantidadeDiasCorridos, pontoCurva.QuantidadeDiasUteis));
            }
        }

        private int RetornarNumeroDias(int codigoTipoBase, int diasCorridos, int diasUteis)
        {
            if (codigoTipoBase == DU252)
            {
                return diasUteis;
            }
            else
            {
                return diasCorridos;
            }
        }

        private int RetornarDiasAnoPorTipoBase(int codigoTipoBase)
        {
            if (codigoTipoBase == DU252)
            {
                return 252;
            }
            else if (codigoTipoBase == DC360)
            {
                return 360;
            }
            else
            {
                return 30;
            }
        }

        private TaxaPontoCurvaVigente RetornaTipoVertice(CurvaVigente curva)
        {
            int VERTICE = 4;

            if (curva != null && curva.lstTaxaPontoCurvaVigente != null)
            {
                return curva.lstTaxaPontoCurvaVigente.FirstOrDefault(t => t.CodigoTipoPontoCurva == VERTICE);
            }

            return null;
        }

        private void ObterParametrosTranscricao()
        {
            _geradorRelatorio = new GeradorRelatorio();
            _configuracoes = new Configuracoes();

            _emailSender = new EmailSender(
                _configuracoes.Email.ObterServidorSMTP(),
                _configuracoes.Email.ObterAssunto(),
                _configuracoes.Email.ObterMensagem(),
                _configuracoes.Email.ObterRemetente(),
                _configuracoes.Email.ObterDestinatarios());

            _tempoEsperaProximaTranscricao =
                _configuracoes.Transcricao.TempoEsperaExecucao;

            _tempoEntreImportacaoExecucao =
                _configuracoes.Transcricao.TempoEntreImportacaoExecucao;

            _tempoToleranciaParaObtencaoYield =
                _configuracoes.Transcricao.TempoToleranciaParaObtencaoYield;
            
            _enviarEmailToleranciaExtrapolada =
                _configuracoes.Transcricao.EnviarEmailToleranciaExtrapolada;

            _statusAtivo = 
                _configuracoes.Transcricao.StatusAtivo;

            _custos =
                _configuracoes.Transcricao.Custos;
        }

        private DateTime ObterProximoHorarioGrade(Curva curva)
        {
            if (curva.GradeExecucaoCurva != null &&
                curva.GradeExecucaoCurva.registroGradeExecucaoCurva != null &&
                curva.GradeExecucaoCurva.registroGradeExecucaoCurva.Count > 0)
            {
                var listaExecucao = from itemGrade in curva.GradeExecucaoCurva.registroGradeExecucaoCurva
                                    where itemGrade.HoraExecucao > _dataHoraAtual
                                    orderby itemGrade.HoraExecucao
                                    select itemGrade;

                RegistroGradeExecucaoCurva registroGrade = listaExecucao.FirstOrDefault();

                if (registroGrade != null)
                {
                    return registroGrade.HoraExecucao;
                }
            }

            return DateTime.MinValue;
        }

        private Curva ObterCurvaIMPorNome(string nomeCurva)
        {
            Curva curvaRetorno = null;
            CurvaBS curvaBS = new CurvaBS();
            string mensagemErro = string.Empty;

            IList<Curva> listaCurva = curvaBS.ObterCurvasPorNome(nomeCurva.Trim());

            if (listaCurva != null)
            {
                int qtdCurvasNome = listaCurva.Count(p => p.NomeCurva.Trim() == nomeCurva.Trim());

                switch (qtdCurvasNome)
                {
                    case 0:
                        mensagemErro = string.Format("Curva de nome {0} não encontrada na base IM", nomeCurva);
                        throw new Exception(mensagemErro);
                    case 1:
                        //Obtém Curva e CurvaVigente
                        curvaRetorno = curvaBS.ObterCurva(listaCurva[0].CodigoCurva);
                        break;
                    default:
                        mensagemErro = string.Format("Existe mais de uma curva de nome {0} na base IM", nomeCurva);
                        throw new Exception(mensagemErro);
                }
            }
            else
            {
                mensagemErro = string.Format("Curva de nome {0} não encontrada na base IM", nomeCurva);
                throw new Exception(mensagemErro);
            }

            return curvaRetorno;
        }

        private IList<Informacao> ObterUltimaPublicacaoYield(string nomeMercado, string nomeYield, DateTime dataReferencia)
        {
            IYieldBS yieldBS = BSFactoryNK.GetYieldBS();
            string mensagemErro = string.Empty;

            IList<Informacao> listaRetorno = yieldBS.ObterInformacoesYield(nomeMercado, nomeYield, dataReferencia);

            return listaRetorno;
        }

        private void ValidarLimiteToleranciaHoraPublicacao(Informacao informacaoYield, string TipoCurvaNK)
        {

            if (TipoCurvaNK == "CUSTOS")
            {
                if (informacaoYield.DataUltimaAlteracao.Date !=  _dataHoraAtual.Date)
                {
                    string mensagemErro = "Data da última publicação da Pre Custos diferente da data atual.";

                    throw new Exception(mensagemErro);
                }

            }

            else
            {
                if (informacaoYield.DataUltimaAlteracao.AddMinutes(_tempoToleranciaParaObtencaoYield) < _dataHoraAtual)
                {
                    string mensagemErro = string.Format("Data/Hora da última publicação da Pre Online superior a {0} minutos do momento atual.", _tempoToleranciaParaObtencaoYield);

                    throw new Exception(mensagemErro);
                }
            }
        }

        private bool DiaUtil(DateTime data)
        {
            return !FinalSemana(data)
                && !FeriadoNacional(data);
        }

        private bool FinalSemana(DateTime data)
        {
            return data.DayOfWeek == DayOfWeek.Saturday
                || data.DayOfWeek == DayOfWeek.Sunday;
        }

        private bool FeriadoNacional(DateTime data)
        {
            CalendarioBS calendario = new CalendarioBS();

            return calendario.EhFeriadoNacional(data.Date);
        }

        private void AguardarProximaTranscricao()
        {
            TimeSpan tempoEspera = _ultimaTranscricao.AddMinutes(_tempoEsperaProximaTranscricao) - _fimTranscricao;

            if (tempoEspera > TimeSpan.Zero)
            {
                Thread.Sleep(Convert.ToInt32(tempoEspera.TotalMilliseconds));
            }
        }

        private void AguardarAteDiaUtil(DateTime dataAtual)
        {
            TimeSpan tempoRestante = DateTime.Today.AddDays(1) - DateTime.Now;

            Thread.Sleep(Convert.ToInt32(tempoRestante.TotalMilliseconds));
        }

        private void SalvarDataHoraUltimaTranscricao()
        {
            _fimTranscricao = DateTime.Now;
        }

        private void CriarTimerPatrol()
        {
            _timerPatrol = new Timer(new TimerCallback(RegistrarLogPatrol), null, 0, PERIODO_PATROL_MINUTOS * 60000);
        }

        private void RegistrarLogPatrol(Object state)
        {
            _logOn.TxtPatrol(ref IDENTFICADOR_ROTINA);
        }

        private void LimparObjetos()
        {
            Logger.RegistrarLog("Fim");
        }
    }

    public static class MetodosEstendidos
    {
        public static DateTime TrimMilisegundos(this DateTime dt)
        {
            if (dt.Millisecond >= 500)
            {
                return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, 0).AddSeconds(1);
            }
            else
            {
                return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, 0);
            }
        }

        public static DateTime TrimMinutos(this DateTime dt)
        {
            DateTime trimmedDt = dt.TrimMilisegundos();

            if (trimmedDt.Minute >= 30)
            {
                return new DateTime(trimmedDt.Year, trimmedDt.Month, trimmedDt.Day, trimmedDt.Hour, trimmedDt.Minute, 0, 0).AddMinutes(1);
            }
            else
            {
                return new DateTime(trimmedDt.Year, trimmedDt.Month, trimmedDt.Day, trimmedDt.Hour, trimmedDt.Minute, 0, 0);
            }
        }
    }
}
